package questions;

import lombok.Data;

import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * JMM可见性、原子性展示
 * 1.volatile保证可见性
 *   number变量，不加volatile，
 * 2.volatile不保证原子性
 *   原子性：不可分割，完整性，即中间不准被插队，防止操作覆盖
 *   如何解决:
 *      sync
 *      Atomic类
 */
public class VolatileDemo {
    public static void main(String[] args) {
        //可以退出
//        withVolatile();
        //不能退出
//        withoutVolatile();
        //不保证原子性的测试，如果保证了原子性，那么这里的值应该打印20000
//        testAtomic();
//        testAtomicIntegerAdd();
//        testAtomicStampedReference();
        testMultiThreadShare();
    }


    private static void testMultiThreadShare() {
        Share share = new Share();
        share.testWithVolatile();
        share.testNoVolatile();
    }

    /**
     *带版本号的AtomicReference，可以解决ABA问题
     */
    public static void testAtomicStampedReference(){
        AtomicReference<Integer> atomicReference = new AtomicReference<>(1);
        System.out.println("ABA问题产生");
        new Thread(()->{
            atomicReference.compareAndSet(1,2);
            System.out.println(Thread.currentThread().getName()+"-->当前数据为：" + atomicReference.get());
            atomicReference.compareAndSet(2,1);
            System.out.println(Thread.currentThread().getName()+"-->当前数据为：" + atomicReference.get());
        },"thread1").start();
        new Thread(()-> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
            }
            atomicReference.compareAndSet(1, 2222);
            System.out.println(Thread.currentThread().getName()+"-->当前数据为：" + atomicReference.get());
        },"thread2").start();
        System.out.println("ABA问题解决");
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }
            atomicStampedReference.compareAndSet(100, 102, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName()+"-->当前数据为：" + atomicStampedReference.getReference()+"\t 版本为："+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(102, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName()+"-->当前数据为：" + atomicStampedReference.getReference()+"\t 版本为："+atomicStampedReference.getStamp());
        },"thread3").start();
        new Thread(()-> {
            int currentStamp= atomicStampedReference.getStamp();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
            }
            boolean result= atomicStampedReference.compareAndSet(100, 9999, currentStamp, currentStamp + 1);
            System.out.println(Thread.currentThread().getName()+"-->修改是否成功：" + result);
            System.out.println(Thread.currentThread().getName()+"-->当前数据为：" + atomicStampedReference.getReference()+"\t 版本为："+atomicStampedReference.getStamp());
        },"thread4").start();

    }

    /**
     *AutomaticReference类似AtomicInteger，不过这里可以操作任意类型进行CAS
     */
    public static void testAtomicReference(){
        AtomicReference<Integer> atomicReference = new AtomicReference<>(1);
        atomicReference.compareAndSet(1,2);
        System.out.println(atomicReference.get());
    }

    public static void testAtomicIntegerAdd(){
        MyDatax myDatax = new MyDatax();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    myDatax.atomicAddPlusPlus();
                }
            }, "thread" + i).start();
        }
        //等待20线程完成后，再取最终的结果值是多少。
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(myDatax.atomicInteger);
    }
    public static void testAtomic(){
        MyDatax myDatax = new MyDatax();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    myDatax.addPlusPlus();
                }
            }, "thread" + i).start();
        }
        //等待20线程完成后，再取最终的结果值是多少。
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(myDatax.number);
        System.out.println(myDatax.number2);
    }
    public static void withoutVolatile(){
        MyDatax myDatax = new MyDatax();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "进入");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
            }
            myDatax.add();
            System.out.println("aaa线程更新了number+" + myDatax.number);
        }, "aaa").start();
        while (myDatax.number == 0) {//在线程aaa修改值以前，将数据拷贝到了main线程中，并进入循环，在3s后，aaa线程修改了值，但是主线程不会退出
            //只要是0，那么一直循环
        }
        System.out.println(Thread.currentThread().getName() + "感知到了变化");
    }

    public static void withVolatile(){
        MyDatax myDatax = new MyDatax();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "进入");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
            }
            myDatax.add();
            System.out.println("aaa线程更新了number+" + myDatax.number2);
        }, "aaa").start();
        while (myDatax.number2 == 0) {
            //只要是0，那么一直循环
        }
        System.out.println(Thread.currentThread().getName() + "感知到了变化,程序退出");
    }
}

@Data
class MyDatax {
    AtomicInteger atomicInteger = new AtomicInteger();
    int number = 0;
    volatile int number2=0;

    public void addPlusPlus() {
        number++;
        number2++;
    }

    public void atomicAddPlusPlus() {
        atomicInteger.addAndGet(1);
    }

    public void add() {
        number = 60;
        number2=60;
    }
}

/**
 * volatile确实会影响可见性.
 * Sleep会导致内存同步，达到类似的效果
 */
class Share{
    public volatile String[] share1=new String[5];

    public String[] getShare1() {
        return share1;
    }

    public String[] getShare2() {
        return share2;
    }

    public String[] share2=new String[5];
    public final Byte value=1;




    public void run1(){
        new Thread(()->{
            while (getShare1()[4]==null){
            }
            System.out.println("share1当前有" + share1 );
        }).start();
    }
    public void run2(){
        new Thread(()->{
            while (share2[4]==null){
//            while (getShare2()[4]==null){
//                try {
//                   //触发同步？
//                   Thread.sleep(0);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
            System.out.println("share2当前有" + share1 );
        }).start();
    }
    public void runAdd1(){
        new Thread(()->{
            int i=0;
            while (share1[4]==null){
                share1[i++]=(UUID.randomUUID().toString());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("share1添加结束,最后一个是："+share1[4]);
        }).start();
    }
    public void runAdd2(){
        new Thread(()->{
            int i=0;
            while (share2[4]==null){
                share2[i++]=(UUID.randomUUID().toString());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("share2添加结束,最后一个为："+share2[4]);
        }).start();
    }
    public void testWithVolatile(){
        run1();
        runAdd1();
    }
    public void testNoVolatile(){
        run2();
        runAdd2();
    }
}